#include <rtthread.h>
#include <drivers/usb_host.h>

struct urb* rt_usbh_urb_alloc(unsigned bufsize, urbcomp_t cb)
{
    struct urb *urb;
    int align;

    if ((bufsize > (1024 * 64)) || !cb)
        return RT_NULL;

    align = RT_ALIGN(sizeof(struct urb), 4);
    urb = rt_malloc(align + bufsize);
    if (urb)
    {
        rt_memset(urb, 0, align);
        rt_list_init(&urb->list);
        rt_list_init(&urb->link);
        urb->comp_cb = cb;

        if (bufsize)
        {
            urb->buf = (void*)(((char*)urb) + align);
            urb->bufsize = bufsize;
        }
    }

    return urb;
}

void rt_usbh_urb_free(struct urb *r)
{
    rt_free(r);
}

int rt_usbh_urb_submit(struct uinstance *dev, struct upipe *pipe, struct urb *r)
{
    int ret;

	if (!r || !r->comp_cb)
		return -EINVAL;

    r->actual_size = 0;
    r->status = 0;
    r->is_working = 0;

    ret = rt_usb_hcd_urb_submit(dev->hcd, pipe, r);
    return ret;
}

int rt_usbh_urb_killall(struct uinstance *dev, struct upipe *pipe)
{
    struct urb *u, *tmp;

    rt_list_for_each_entry_safe(u, tmp, struct urb, &pipe->urbs, list)
    {
        if (rt_list_isempty(&u->link))
            continue;

        rt_usb_hcd_urb_cancel(dev->hcd, pipe, u);
        rt_usb_hcd_event_urb_complete(u, -ENOENT);
    }

    return 0;
}

int rt_usbh_urb_allocall(struct upipe *pipe, unsigned bufsize, urbcomp_t cb, void *context, int n)
{
    int ret = -1;
    int i;
    struct urb* u;

    if (!pipe || n <= 0)
        return -1;

    for (i = 0; i < n; i ++)
    {
        u = rt_usbh_urb_alloc(bufsize, cb);
        if (!u)
            goto _out;

        u->context = context;
        rt_list_insert_before(&pipe->urbidle, &u->link);
        rt_list_insert_before(&pipe->urbs, &u->list);
    }
    ret = 0;

_out:
    if (ret != 0)
    {
        rt_list_for_each_entry(u, struct urb, &pipe->urbidle, link)
        {
            rt_usbh_urb_free(u);
        }
    }

    return ret;
}

int rt_usbh_urb_freeall(struct uinstance *dev, struct upipe *pipe)
{
    struct urb *u, *tmp;

    rt_list_for_each_entry_safe(u, tmp, struct urb, &pipe->urbs, list)
    {
        rt_list_remove(&u->list);
        if (u->status != -ENOENT)
        {
            rt_usb_hcd_urb_cancel(dev->hcd, pipe, u);
            rt_usb_hcd_event_urb_complete(u, -ENOENT);            
        }
        rt_usbh_urb_free(u);
    }

    return 0;
}

struct urb* rt_usbh_urb_idle_take(struct upipe *pipe)
{
    struct urb *ret = RT_NULL;

    if (!rt_list_isempty(&pipe->urbidle))
    {
        ret = rt_list_first_entry(&pipe->urbidle, struct urb, link);
        rt_list_remove(&ret->link);
    }

    return ret;
}
